import $get from 'lodash/get'
import $assign from 'lodash/assign'
import $isPlainObject from 'lodash/isPlainObject'
import $isFunction from 'lodash/isFunction'
import { Toast } from '@tencent/corgi-ui'
import $bus from '../model/bus'
import $logger from '../util/logger'

const ERROR_MSG_UNKNOWN = '网络错误，请检查后重试'
const ERROR_MSG_NET = '请求失败，请检查网络'
const ERROR_MSG_EMPTY = '无数据返回'
const ERROR_MSG_STRUCT = '接口返回格式错误'

const logger = $logger('request')

function ajax (spec) {
	const $ = window.Zepto
	spec = spec || {}
	return new Promise((resolve, reject) => {
		let conf = $assign({}, spec, {
			success (response, status, xhr) {
				let res = {}
				res.response = response
				res.status = status
				res.xhr = xhr
				resolve(res)
			},
			error (xhr, errorType, err) {
				console.log(xhr, errorType, err)
				let ajaxErr = null
				if (typeof err === 'string') {
					ajaxErr = new Error(err)
				} else if (err instanceof Error) {
					ajaxErr = err
				} else {
					ajaxErr = new Error(ERROR_MSG_UNKNOWN)
				}
				ajaxErr.xhr = xhr
				ajaxErr.errorType = errorType
				reject(ajaxErr)
			}
		})
		$.ajax(conf)
	})
}

function request (options) {
	let extOptions = {
		// 跨域携带cookie
		withCredentials: true,
		// 是否自定义错误处理流程
		customError: false,
		// 替换默认的错误消息处理方式
		resolveErrorMsg: null,
		// 实现数据兜底
		resolveBackup: null,
		// 映射返回的数据结构
		// 值为键对应的属性路径
		formatResult: null
	}

	let conf = {
		url: '',
		type: 'get',
		dataType: 'json',
		...extOptions,
		...options
	}

	if (conf.type && typeof conf.type === 'string') {
		conf.type = conf.type.toUpperCase()
	}

	let resolveErrorMsg = conf.resolveErrorMsg
	if (typeof resolveErrorMsg !== 'function') {
		resolveErrorMsg = function (msg) {
			if (msg.indexOf('15006') >= 0) {
				Toast.fail('(15006)请重新登录', 2)
			} else {
				Toast.fail(msg, 2)
			}
		}
	}

	if (conf.withLoading) {
		$bus.emit('loading-start')
	}

	// 清理选项
	let spec = Object.assign({}, conf)
	Object.keys(extOptions).forEach(key => {
		delete spec[key]
	})

	// 解决跨域请求配置 withCredentials 的问题
	if (conf.withCredentials) {
		if (!spec.xhrFields) {
			spec.xhrFields = {}
		}
		spec.crossDomain = true
		spec.xhrFields.withCredentials = conf.withCredentials
	}

	let pm = ajax(spec).then(res => {
		if (conf.withLoading) {
			$bus.emit('loading-end')
		}
		let response = $get(res, 'response')
		let ajaxStatus = $get(res, 'status')
		let xhrStatus = $get(res, 'xhr.status') || ''
		let xhrText = $get(res, 'xhr.responseText') || ''
		logger.log('request res:', response, ajaxStatus, xhrStatus)
		let result

		if (response) {
			let rs = {}
			if ($isPlainObject(conf.formatResult)) {
				Object.keys(conf.formatResult).forEach(key => {
					let propPath = conf.formatResult[key]
					rs[key] = $get(response, propPath)
				})
			} else if ($isFunction(conf.formatResult)) {
				rs = conf.formatResult(response)
			} else {
				rs = response
			}

			if (conf.customError) {
				result = rs
			} else if (rs.ret === 0) {
				result = rs.data
			} else if (rs.msg) {
				result = Promise.reject(new Error(`(${rs.ret})${rs.msg}`))
			} else if (rs.ret) {
				result = Promise.reject(new Error(`(${rs.ret})请求失败`))
			} else {
				result = Promise.reject(new Error(`[${xhrStatus}]${ERROR_MSG_STRUCT}:${xhrText}`))
			}
		} else {
			result = Promise.reject(new Error(`[${xhrStatus}]${ERROR_MSG_EMPTY}`))
		}
		return result
	}).catch(err => {
		if (conf.withLoading) {
			$bus.emit('loading-end')
		}

		let msg = $get(err, 'message') || ERROR_MSG_NET
		let status = $get(err, 'xhr.status') || ''
		let type = $get(err, 'errorType') || ''
		if (status) {
			msg = `[${status}]${msg}`
		} else if (type) {
			msg = `${msg}`
		}

		if (err instanceof Error) {
			setTimeout(() => {
				throw err
			}, 20)
		} else {
			setTimeout(() => {
				throw new Error(msg)
			}, 20)
		}

		// 允许接口自定义错误消息的处理
		resolveErrorMsg(msg)

		let backupData = null
		if (typeof conf.resolveBackup === 'function') {
			backupData = conf.resolveBackup(msg)
		}

		// 允许自定义接口错误后使用的兜底数据
		if (backupData === null) {
			return Promise.resolve()
		} else {
			return Promise.resolve(backupData)
		}
	})
	return pm
}

export default request
